--
-- either one of the sync signals
--
--       |<-- Active Region ---->|<----------- Blanking Region ---------->|
--       |      (Pixels)         |                                        |
--       |                       |                                        |
--       |                       |                                        |
--   ----+--------- ... ---------+-------------             --------------+---
--   |   |                       |            |             |             |
--   |   |                       |<--Front    |<---Sync     |<---Back     |
--   |   |                       |    Porch-->|     Time--->|    Porch--->|
-- ---   |                       |            ---------------             |
--       |                       |                                        |
--       |<------------------- Period ----------------------------------->|
--

package VGACore(
        VGATiming(..), VGAHVTiming(..),
        vga640x480, vga1280x480, sizeToTiming, hzToTiming,
        mkVGACore, VGACore(..)
        ) where

-- Sync info for horizontal or vertical
struct VGAHVTiming =
     activeSize :: Integer
     syncStart  :: Integer
     syncEnd    :: Integer
     totalSize  :: Integer

-- Sync info for VGA
struct VGATiming =
     h :: VGAHVTiming
     v :: VGAHVTiming

-- Better?
-- 640  650  710  762    480  482  488  500
-- 800  812  888  952    600  602  610  626

vga640x480 :: VGATiming
vga640x480 =
    VGATiming {
        h = VGAHVTiming { activeSize = 640; syncStart = 664; syncEnd = 760; totalSize = 800 };
        v = VGAHVTiming { activeSize = 480; syncStart = 490; syncEnd = 494; totalSize = 526 };
    }

vga1280x480 :: VGATiming
vga1280x480 =
    VGATiming {
        h = VGAHVTiming { activeSize = 1280; syncStart =1328; syncEnd = 1520; totalSize = 1600 };
        v = VGAHVTiming { activeSize = 480; syncStart = 490; syncEnd = 494; totalSize = 526 };
    }


hzToTiming :: Integer -> VGATiming
hzToTiming hz =
  let
    nsCycles :: Integer -> Integer
    nsCycles x = (x * hz) `div` 1000000000

    usCycles :: Integer -> Integer
    usCycles x = (x * hz) `div` 1000000

    hTotalSize = nsCycles 31770
  in
    VGATiming {
        h = VGAHVTiming { activeSize = nsCycles 25170;
                          syncStart  = nsCycles 26110;
                          syncEnd    = nsCycles 29880;
                          totalSize  = hTotalSize;
                        };
        v = VGAHVTiming { activeSize = usCycles 15250 `div` hTotalSize;
                          syncStart  = usCycles 15700 `div` hTotalSize;
                          syncEnd    = usCycles 15764 `div` hTotalSize;
                          totalSize  = usCycles 16784 `div` hTotalSize;
                        }
    }


sizeToTiming :: Integer -> Integer -> VGATiming
sizeToTiming hSize vSize =
    VGATiming {
        h = VGAHVTiming { activeSize = hSize;
                          syncStart = (hSize * 856) `div` 800;
                          syncEnd = (hSize * 880) `div` 800;
                          totalSize = (hSize * 982) `div` 800 };
        v = VGAHVTiming { activeSize = vSize;
                          syncStart = (vSize * 602) `div` 600;
                          syncEnd = (vSize * 610) `div` 600;
                          totalSize = (vSize * 626) `div` 600 };
    }
{-
    VGATiming {
        h = VGAHVTiming { activeSize = hSize;
                          syncStart = (hSize * 812) `div` 800;
                          syncEnd = (hSize * 888) `div` 800;
                          totalSize = (hSize * 952) `div` 800 };
        v = VGAHVTiming { activeSize = vSize;
                          syncStart = (vSize * 602) `div` 600;
                          syncEnd = (vSize * 610) `div` 600;
                          totalSize = (vSize * 626) `div` 600 };
    }
-}

interface VGACore hCoord vCoord =
   not_hsync :: Bool
   not_vsync :: Bool
   blank :: Bool

   hPos  :: hCoord
   vPos  :: vCoord

   lineTick :: Bool
   frameTick :: Bool


mkVGACore :: (Bits hCoord hs, Literal hCoord, Eq hCoord, Arith hCoord,
              Bits vCoord vs, Literal vCoord, Eq vCoord, Arith vCoord) =>
             Integer -> VGATiming -> Module (VGACore hCoord vCoord)
mkVGACore preScale vt = do
    hPosR :: Reg hCoord <- mkReg 0
    vPosR :: Reg vCoord <- mkReg 0

    hVisible :: Reg Bool <- mkReg True -- False
    vVisible :: Reg Bool <- mkReg True -- False

    not_hsyncR :: Reg Bool <- mkReg True
    not_vsyncR :: Reg Bool <- mkReg True

    scale :: Reg (Bit 4) <- mkReg 0

    let
        hSize = fromInteger vt.h.activeSize
        vSize = fromInteger vt.v.activeSize
        hSyncStart = fromInteger vt.h.syncStart
        vSyncStart = fromInteger vt.v.syncStart
        hSyncEnd = fromInteger vt.h.syncEnd
        vSyncEnd = fromInteger vt.v.syncEnd
        hTotal = fromInteger vt.h.totalSize
        vTotal = fromInteger vt.v.totalSize

        hTickLocal = hPosR == hTotal
        vTickLocal = hTickLocal && vPosR == vTotal

        hTickExternal = hPosR == hSize + 1
        vTickExternal = hTickExternal && vPosR == vSize + 1


    addRules $
        rules
          {-# ASSERT no implicit conditions #-}
          {-# ASSERT fire when enabled #-}
          "tick":
              when preScale /= 1 ==>
              action
                  scale := if scale == 0 then fromInteger (preScale - 1) else scale - 1

          {-# ASSERT no implicit conditions #-}
          {-# ASSERT fire when enabled #-}
          "hclk":
              when preScale == 1 || scale == 0 ==>
              action
                hPosR := if hTickLocal then 0 else hPosR + 1
                hVisible := hPosR /= hSize && (hTickLocal || hVisible)

          {-# ASSERT no implicit conditions #-}
          {-# ASSERT fire when enabled #-}
          "vclk":
              when hTickLocal ==>
              action
                vPosR := if vTickLocal then 0 else vPosR + 1
                vVisible := vPosR /= vSize && (vTickLocal || vVisible)

          {-# ASSERT no implicit conditions #-}
          {-# ASSERT fire when enabled #-}
          "hsyncOn":
              when hPosR == hSyncStart ==>
              action
                not_hsyncR := False

          {-# ASSERT no implicit conditions #-}
          {-# ASSERT fire when enabled #-}
          "hsyncOff":
              when hPosR == hSyncEnd ==>
              action
                not_hsyncR := True

          {-# ASSERT no implicit conditions #-}
          {-# ASSERT fire when enabled #-}
          "vsyncOn":
              when vPosR == vSyncStart ==>
              action
                not_vsyncR := False

          {-# ASSERT no implicit conditions #-}
          {-# ASSERT fire when enabled #-}
          "vsyncOff":
              when vPosR == vSyncEnd ==>
              action
                not_vsyncR := True

    return $
      interface VGACore

        hPos = hPosR
        vPos = vPosR

        -- blank video outside of visible region
        blank = not (hVisible && vVisible)

        not_hsync = not_hsyncR
        not_vsync = not_vsyncR

        lineTick = hTickExternal && vVisible
        frameTick = vTickExternal
